#include "stdafx.h"
#include "ListData.h"
#include "PoomData.h"
#include "PoomPictureLoader.h"
#include "MenuBar.h"
#include "GraphicFunctions.h"

#ifdef _DEBUG
#include "crtdbg.h"
#endif

extern TCHAR g_szVersion[];
extern int   g_nVersionLen;

#define LIST_INDICATOR_OFFSET_Y     10

CListBase::CListBase(HWND hParent)
  : CList(hParent, Theme::iListItemSize)
{
  listIndOverride = 0;
  isFiltered = false;

  style.clrBackground1 = Theme::rgbListItemBackground;
  style.clrBackground2 = Theme::rgbListItemBackground;
  style.hbrBackground  = Theme::hbrListBackground;
  headerHeight = Theme::iListGroupSize;
  headerText[0] = 0;
  headerTextLen = 0;
  extraScrollHeight = 0;
}

CListBase::~CListBase()
{
  if (this->isFiltered)
  {
    this->items.clear();
    this->items = this->allItems;
    this->allItems.clear();
  }
}

void CListBase::Clear()
{
  ClearFilter();
  listIndOverride = 0;
  headerText[0] = 0;
  headerTextLen = 0;
  extraScrollHeight = 0;

  CList::Clear();
}

bool CListBase::OnMouseDown(int x, int y)
{
  CList::OnMouseDown(x, y);

  if (!isMinimizing && x > rect.right - Theme::iListScrollbarWidth)
  {
    longTapTimer.Stop();
    SetSelection();
    SetHover();
    ScrollBar(y - rect.top);
  }

  return true;
}

bool CListBase::OnMouseMove(int x, int y)
{
  if (isMinimizing && 
      x > rect.right - Theme::iListScrollbarWidth &&
      abs(y - rect.top - lastY) > Settings::scrollThresh)
  {
    longTapTimer.Stop();
    SetSelection();
    SetHover();
    isMinimizing = false;
    ScrollBar(y - rect.top);
    return true;
  }
  if (isScrolling)
  {
    ScrollBar(y - rect.top);
    return true;
  }

  return CList::OnMouseMove(x, y);
}

bool CListBase::OnKeyDown(int vKey, bool bRepeating)
{
  switch(vKey)
  {
    case VK_UP:
      if (selection < 0)
      {
        SetSelection(GetItemIdx(scrolled));
        SetHover(this->selection);
        ScrollToSelection();
      }
      else if (bRepeating)
      {
        // Jump to the previous group
        int sel = selection;
        if (sel == 0)
          return true;

        do
        {
          sel--;
        }
        while (sel > 0 && !IsItemNewGroup(sel));

        if (sel == 0 || (sel > 0 && IsItemNewGroup(sel)))
        {
          if (nKeyRepeatCount++ % 5 != 0) return true;
          SetSelection(sel);
          SetHover(this->selection);

          ScrollToSelectionFast();  // quickly jump to next section
        }
        else
        {
          SelectPrev();
        }
      }
      else
      {
        SelectPrev();
      }
      return true;

    case VK_DOWN:
      if (selection < 0)
      {
        SetSelection(GetItemIdx(scrolled));
        SetHover(this->selection);
        ScrollToSelection();
      }
      else if (bRepeating)
      {
        // Jump to the next group
        int sel = selection;
        if (sel >= (int)items.size() - 1)
          return true;

        do
        {
          sel++;
        }
        while (sel < (int)items.size() && !IsItemNewGroup(sel));

        if (sel < (int)items.size() && IsItemNewGroup(sel))
        {
          if (nKeyRepeatCount++ % 5 != 0) return true;
          SetSelection(sel);
          SetHover(this->selection);

          ScrollToSelectionFast();  // quickly jump to next section
        }
        else
        {
          SelectNext();
        }
      }
      else
      {
        SelectNext();
      }
      return true;
  }

  return CList::OnKeyDown(vKey, bRepeating);
}

void CListBase::SelectPrev()
{
  // This method modifies scrollto values so that d-pad scrolled items don't become obscured by the group overlay when the scrolling pages
  if ((selection - 1) > 0 && items[selection - 1]->GetHeight() == defaultItemHeight) 
  {
    posOffset = 0 - headerHeight;
  }
  else
  {
    posOffset = 0;
  }

  CList::SelectPrev();
}

void CListBase::SelectNext()
{
  // This method modifies scrollto values so that d-pad scrolled items don't become obscured by the group overlay when the scrolling pages
  if ((selection + 1) < (int)this->items.size() && items[selection + 1]->GetHeight() == defaultItemHeight) 
  {
    posOffset = 0 - headerHeight;
  }
  else
  {
    posOffset = 0;
  }

  CList::SelectNext();
}


void CListBase::CalculateHeights()
{
  int c = 0;
  int count = (int)items.size();
  itemPos.clear();
  groupPos.Clean();

  showGroupHeaders = (Info_GetCurrentTab(parentWnd) != tabFavorites || Settings::enableFavoritesSearch);

  if (Info_GetCurrentTab(parentWnd) == tabContacts && !Settings::showLettersInContacts)
  {
      showGroupHeaders = false;
  }

  if (count <= 0)
  {
    itemPos.push_back(0);
    height = 0;
    avgItemHeight = defaultItemHeight;
    return;
  }

  itemPos.reserve(count + 1);

  for (int i = 0; i < count; i++)
  {
    itemPos.push_back(c);

    int h = defaultItemHeight;

    if (showGroupHeaders && IsItemNewGroup(i) && BaseItem(i)->wcGroup)
    {
      h += Theme::iListGroupSize;
      groupPos.Add(BaseItem(i)->wcGroup, c);
    }

    items[i]->SetHeight(h);
    items[i]->SetIndicatorHeight(defaultItemHeight);

    c += h;
  }

  if (showGroupHeaders)
    showGroupHeaders = groupPos.Count() > 0;

  itemPos.push_back(c);
  height = c + extraScrollHeight;
  avgItemHeight = height / count;
  UpdateScrollMax();
}

void CListBase::Draw(HDC hdc, HDC hdcTmp, const RECT& rClip)
{
  RECT rItem = rect;

  rItem.top = max(rClip.top, rect.top);
  rItem.bottom = min(rClip.bottom, rect.bottom);
  FillRect(hdc, &rClip, Theme::hbrListBackground);

  if (items.size() == 0)
  {
    if (Settings::showVersionInfo)
    {
      SelectObject(hdc, Theme::SecondaryListFont);
      SetTextAlign(hdc, TA_LEFT);
      SetTextColor(hdc, Theme::rgbListItemSelectedShadow);

      ExtTextOut(hdc, rect.left, rect.bottom - Theme::SecondaryListFontSize, NULL, NULL, g_szVersion, g_nVersionLen, 0);
    }

    return;
  }

  int sItem, Item;
  int count = (int)items.size();
  int yOffset = scrolled + (int)scrollOffset;

  Item = yOffset < 0 ? 0 : GetItemIdx(yOffset);
  if (Item == -1)
    Item = (int)items.size()-1;
  sItem = Item;
  rItem.bottom = rect.top + itemPos[Item] - yOffset;

  SetBkMode(hdc, TRANSPARENT);

  // ******* DRAW BACKGROUND BEFORE LIST ITEMS
  if (rItem.bottom > rClip.top)
  {
    RECT rBack = rClip;
    rBack.top = max(rClip.top, rect.top);
    rBack.bottom = rItem.bottom;
    FillRect(hdc, &rBack, Theme::hbrListBackground);
  }

  // ******* DRAW
  for (int odi = 0; odi < odiCount; odi++)
  {
    switch(odi)
    {
    case odiBackground:
      break;
    case odiGroupHeader:
      SelectObject(hdc, Theme::GroupFont);
      SetTextAlign(hdc, TA_LEFT);
      break;
    case odiPrimaryText:
      SelectObject(hdc, Theme::PrimaryListFont);
      SetTextAlign(hdc, TA_LEFT);
      break;
    case odiDialingNo:
      SelectObject(hdc, Theme::DialingNoListFont);
      SetTextAlign(hdc, TA_LEFT);
      break;
    case odiPicture:
      break;
    case odiSecondaryText:
      SelectObject(hdc, Theme::SecondaryListFont);
      SetTextAlign(hdc, TA_CENTER);
      break;
    }

    Item = sItem;
    rItem.top = max(rClip.top, rect.top);
    rItem.bottom = rect.top + itemPos[Item] - yOffset;

    while (Item < count && rItem.top <= rClip.bottom)
    {
      rItem.top = rItem.bottom;
      rItem.bottom = rItem.top + itemPos[Item+1] - itemPos[Item];

      if (rItem.bottom >= rClip.top)
      {
        if (odi == odiGroupHeader)
        {
          if (!showGroupHeaders || !IsItemNewGroup(Item) || !BaseItem(Item)->wcGroup || rItem.top < rect.top)
          {
            Item++;
            continue;
          }
        }

        BaseItem(Item)->OptDrawItemOn(parentWnd, hdc, rItem, (OptDrawItem)odi);
      }

      // Next Item
      Item++;
    }
  }

  // ******* DRAW BACKGROUND AFTER LIST ITEMS
  if (rItem.bottom < rClip.bottom && rItem.bottom < rect.bottom)
  {
    RECT rBack = rClip;
    rBack.top = rItem.bottom;
    rBack.bottom = min(rect.bottom, rClip.bottom);
    FillRect(hdc, &rBack, Theme::hbrListBackground);

    if (Settings::showVersionInfo)
    {
      SelectObject(hdc, Theme::SecondaryListFont);
      SetTextAlign(hdc, TA_LEFT);
      SetTextColor(hdc, Theme::rgbListItemSelectedShadow);

      ExtTextOut(hdc, rect.left, rect.bottom - Theme::SecondaryListFontSize, NULL, NULL, g_szVersion, g_nVersionLen, 0);
    }
  }
}

int CListBase::GetItemIdxByOid(CEOID oid) const
{
  size_t n = items.size();
  for (size_t i = 0; i < n; ++i)
  {
    if (BaseItem(i)->oId == oid)
      return i;
  }
  return -1;
}

bool CListBase::IsItemNewGroup(int index) const
{
  if (index <= 0)
    return true;
  if (index >= (int)this->items.size())
    return false;

  return BaseItem(index - 1)->wcGroup != BaseItem(index)->wcGroup;
}

void CListBase::SelectItemByGroup(int groupIdx)
{
  if (groupIdx >= 0 && groupIdx < groupPos.Count())
  {
    SetSelection(GetItemIdx(groupPos.GetPosByIndex(groupIdx)));
    SetHover(this->selection);
  }
}

void CListBase::ApplyFilter(const SearchKbdKeys* filter, int len, TCHAR match[], bool redraw)
{
  match[0] = 0;
  if (filter == NULL)
    return;

  int sel = FilterIdxToListIdx(selection);
  SetSelection(-1, false);
  SetHover(-1, false);

  if (this->allItems.size() == 0)
  {
    this->isFiltered = true;
    this->allItems = this->items;
  }
  this->items.clear();

  TCHAR filterChars[SKK_MAX_STRING_LENGTH];
  SearchAlphabetMgr::FilterToChars(filter, len, filterChars, SKK_MAX_STRING_LENGTH);

  if (len == 0)
  { // add all
    this->items = this->allItems;
  }
  else
  { // add matching
	size_t n = this->allItems.size();
    for (size_t i = 0; i < n; i++)
    {
      int pos = SearchAlphabetMgr::GetAlphabet().FindFilter(((CListItemBase*)allItems[i])->szPrimaryText, filterChars, len);
      if (pos >= 0)
      {
        if (match[0] == 0)
        {
          StringCchCopyN(match, SKK_MAX_STRING_LENGTH, ((CListItemBase*)allItems[i])->szPrimaryText + pos, len);
        }
        this->items.push_back(this->allItems[i]);
      }
    }
    if (match[0] == 0)
    {
      int i;
      for (i = 0; i < SKK_MAX_STRING_LENGTH && i < len; i++)
      {
        match[i] = TEXT('?');
      }
      match[i] = 0;
    }
  }

  SetSelection(ListIdxToFilterIdx(sel), false);
  SetHover(this->selection, false);
  CalculateHeights();
  if (this->selection != -1)
    ScrollToSelection();
  else if (scrolled != 0)
    ScrollTo(0, Settings::scrollSearchTime);
  if (redraw)
    Redraw();
}

void CListBase::ApplyFilter(const TCHAR* filter, bool redraw)
{
  if (filter == NULL)
    return;

  int sel = FilterIdxToListIdx(selection);
  SetSelection(-1, false);
  SetHover(-1, false);

  if (this->allItems.size() == 0)
  {
    this->isFiltered = true;
    this->allItems = this->items;
  }
  this->items.clear();

  if (filter[0] == 0)
  { // add all
    this->items = this->allItems;
  }
  else
  { // add matching
    TCHAR lname[PRIMARY_TEXT_LENGTH];
    TCHAR lfilter[SKK_MAX_STRING_LENGTH];
    StringCchCopy(lfilter, SKK_MAX_STRING_LENGTH, filter);
    _tcslwr(lfilter);
	size_t n = this->allItems.size();
    for (size_t i = 0; i < n; i++)
    {
      // TODO: This is where additional searches are possible
      StringCchCopy(lname, PRIMARY_TEXT_LENGTH, ((CListItemBase*)allItems[i])->szPrimaryText);
      _tcslwr(lname);
  
      int pos = SearchAlphabetMgr::GetAlphabet().FindText(lname, lfilter);
      if (pos >= 0)
      {
        this->items.push_back(this->allItems[i]);
      }
    }
  }

  SetSelection(ListIdxToFilterIdx(sel), false);
  SetHover(this->selection, false);
  CalculateHeights();
  if (this->selection != -1)
    ScrollToSelection();
  else if (scrolled != 0)
    ScrollTo(0, Settings::scrollSearchTime);
  if (redraw)
    Redraw();
}

void CListBase::ClearFilter()
{
  if (this->isFiltered)
  {
    int sel = FilterIdxToListIdx(selection);
    SetSelection(-1, false);
    SetHover(-1, false);

    this->isFiltered = false;

    this->items.clear();
    this->items = this->allItems;

    this->allItems.clear();

    CalculateHeights();

    SetSelection(sel);
    SetHover(this->selection, false);
  }
}

int CListBase::FilterIdxToListIdx(int idx)
{
  if (!this->isFiltered)
    return idx;

  size_t n = this->items.size();
  if (idx < 0 || idx >= (int)n)
    return -1;

  for (size_t i = 0; i < n; i++)
  {
    if (allItems[i] == items[idx])
      return i;
  }

  return -1;
}

int CListBase::ListIdxToFilterIdx(int idx)
{
  if (!this->isFiltered)
    return idx;

  size_t n = this->allItems.size();
  if (idx < 0 || idx >= (int)n)
    return -1;

  for (size_t i = 0; i < n; i++)
  {
    if (items[i] == allItems[idx])
      return i;
  }

  return -1;
}

void CListBase::ScrollBar(int y)
{
  if (height <= rect.bottom - rect.top)
    return;

  StopScrolling();

  isScrolling = true;
  abortKineticScroll = true;
  scrollSpeed = 25;

  // if in "Contacts" and setting is enabled, scroll by chunks of A,B,C,etc.
  if (showGroupHeaders && Settings::scrollBarContactsByLetter && Info_GetCurrentTab(parentWnd) != tabCallLog)
  {
    int index = y * groupPos.Count() / (rect.bottom-rect.top);
    scrolled = index < 0 ? 0
          : index >= groupPos.Count() ? scrollMax
          : groupPos.GetPosByIndex(index);
  }
  // otherwise, just do a normal scroll
  else
  {
    double pct = (double)y / (double)(rect.bottom-rect.top);
    scrolled = (int)(scrollMax * pct);
  }

  scrolled = min(scrolled, scrollMax);
  scrolled = max(scrolled, 0);

  Redraw();
}

void CListBase::ScrollTo(int pos, int time)
{
  CList::ScrollTo(pos, time);

  // override list indicator when in Contacts mode
  if (showGroupHeaders && Info_GetCurrentTab(parentWnd) != tabCallLog)
  {
    scrollSpeed = 25;
    int index = GetItemIdx(pos);
    if (index != -1)
      listIndOverride = BaseItem(index)->wcGroup;
  }
}

void CListBase::StopScrolling()
{
  CList::StopScrolling();
  listIndOverride = 0;
}

void CListBase::DrawHeader(HDC hdc, HDC hdcTmp, const RECT& rClip)
{
  RECT rc = fullRect;
  rc.bottom = rc.top + headerHeight;
  SelectObject(hdc, Theme::GroupFont);
  SetTextAlign(hdc, TA_CENTER);
  SetBkMode(hdc, TRANSPARENT);
  SetTextColor(hdc, Theme::rgbListGroupText);
  FillRect(hdc, &rc, Theme::hbrListGroupBackground);

  // separator
  RECT rSep = rc;
  rSep.top = rc.bottom - LIST_SEPARATOR_HEIGHT;
  FillRect(hdc, &rSep, Theme::hbrListItemSeparator);

  // ******* Draw Group Header Text
  ExtTextOut(hdc, (rc.right + rc.left) / 2, 
        rc.top - 1 + ((Theme::iListGroupSize - Theme::GroupFontSize) / 2),
        NULL, NULL, headerText, headerTextLen, 0);
}

void CListBase::DrawOverlays(HDC hdc, HDC hdcTmp, const RECT& rClip)
{
  if (items.size() == 0)
    return;

  int sItem = scrolled < 0 ? 0 : GetItemIdx(scrolled);
  if (sItem == -1)
    sItem = (int)items.size() - 1;

  // Draw the group of the list item that's at the top of the list
  if (showGroupHeaders && scrolled >= 0 &&
      rClip.top < (rect.top + Theme::iListGroupSize))
  {
    const CListItemBase* cItem = BaseItem(sItem);
    if (cItem->wcGroup)
    {
      RECT rTopGroup = {0, 0, rect.right - rect.left, Theme::iListGroupSize};
      SelectObject(hdcTmp, Theme::GroupFont);
      SetTextAlign(hdcTmp, TA_LEFT);
      SetBkMode(hdcTmp, TRANSPARENT);
      cItem->OptDrawGroup(parentWnd, hdcTmp, rTopGroup);

      int nHeight = Theme::iListGroupSize;
      int nBottom = nHeight;

      if (IsItemNewGroup(sItem + 1))
      {
        nBottom = min(nBottom, itemPos[sItem + 1] - scrolled);
      }

      // account for the fact that the list
      // doesn't start at the top of the screen
      nBottom += rect.top;

      int nLeft = rect.left;
      int nWidth = rect.right - rect.left;
      int nTop = nBottom - nHeight;

      if (Settings::optimizedAlpha && IsMoving())
      {
        BitBlt(hdc, nLeft, nTop, nWidth, nHeight, hdcTmp, 0, 0, SRCCOPY);
      }
      else
      {
        GraphicFunctions::BltAlpha(hdc, nLeft, nTop, nWidth, nHeight, hdcTmp, 0, 0, 200);
      }
    }
  }

  // Draw letter indicator
  if (isScrolling && fabs(scrollSpeed) > 20 && !bHideLetterOverlay)
    DrawScrollInd(hdc, rClip);

  // Draw Scrollbar
  if (Theme::iListScrollbarIndicatorWidth > 0)
    DrawScrollBar(hdc, rClip);

  // Draw the header
  if (headerHeight > 0 && headerTextLen > 0)
    DrawHeader(hdc, hdcTmp, rClip);
}

void CListBase::DrawScrollBar(HDC hdc, const RECT& rClip)
{
  RECT rBar = rect;
  rBar.left = rBar.right - Theme::iListScrollbarIndicatorWidth;
  FillRect(hdc, &rBar, Theme::hbrListBackground);

  if (height == 0)
    return;

  rBar.top = rect.top + scrolled * (rect.bottom-rect.top) / height;
  rBar.bottom = rBar.top + (rect.bottom-rect.top) * (rect.bottom-rect.top) / height;
  FillRect(hdc, &rBar, Theme::hbrListScrollbarIndicator);
}

void CListBase::DrawScrollInd(HDC hdc, const RECT& rClip)
{
  if (rClip.bottom < rect.top ||
      rClip.top >= rect.top + LIST_INDICATOR_OFFSET_Y)
    return;

  TCHAR buffer[GROUP_TEXT_LENGTH];
  if (listIndOverride == 0)
  {
    int item = GetItemIdx(scrolled);
    if (item != -1)
      BaseItem(item)->GetGroupIndicator(parentWnd, buffer);
    else
      return;
  }
  else
  {
    buffer[0] = listIndOverride;
    buffer[1] = 0;
  }
  int length = _tcslen(buffer);

  if (length > 0)
  {
    SelectObject(hdc, Theme::ListIndicatorFont);
    SetTextAlign(hdc, TA_CENTER);
    SetTextColor(hdc, Theme::rgbListIndicatorText);
    SetBkMode(hdc, TRANSPARENT);

    ExtTextOut(hdc, (rect.right + rect.left) / 2, 
      rect.top + LIST_INDICATOR_OFFSET_Y, NULL, NULL, buffer, length, 0);
  }
}

void CListBase::SetRect(const RECT& rc)
{
  CopyRect(&fullRect, &rc);
  RECT rcList = rc;
  rcList.top += headerTextLen > 0 ? headerHeight : 0;
  CList::SetRect(rcList);
}

void CListBase::SetHeaderText(const TCHAR* szText)
{
  ::StringCchCopy(headerText, HEADER_TEXT_LENGTH, szText);
  headerTextLen = ::_tcslen(headerText);
  SetRect(fullRect);
}

void CListBase::ClearHeaderText()
{
  headerText[0] = 0;
  headerTextLen = 0;
  SetRect(fullRect);
}